#include <stdio.h>
#include <stdint.h>
#include <pthread.h>
#include <string.h>

#include <xmpp/xmpp.h>
#include <xmpp/roster.h>
#include <xmpp/jinglep2p.h>

#include "eloop.h"
#include "ca_types.h"
#include "descrambler.h"
#include "ca_module.h"

#define TEST_ID 0x3000

static int  p2p_module_init(void);
static void p2p_module_deinit(void);
static void p2p_ecm_arrived(uint8_t *section, int typs, short pid);
static void p2p_emm_arrived(uint8_t *section);

ca_module_t this_ca_module = {
  .system_id = TEST_ID,

  .module_init = p2p_module_init,
  .module_deinit = p2p_module_deinit,

  .on_ecm_arrived = p2p_ecm_arrived,
  .on_emm_arrived = p2p_emm_arrived,
};

static int thread_create(pthread_t *handle, void *(*start)(void *), void *param, int deatch);
static void* xmpp_service_loop(void *param);
static int   xmpp_send_ecm(uint8_t *section, int types, short pid);

static int
p2p_module_init(void)
{
  pthread_t th;

  eloop_t *xloop = eloop_init();
  thread_create(&th, xmpp_service_loop, xloop, 1);

  return 1;
}

static void
p2p_module_deinit(void)
{
}

static void
p2p_ecm_arrived(uint8_t *section, int types, short pid)
{
  printf("%s\n", __func__);
  xmpp_send_ecm(section, types, pid);
}

static void
p2p_emm_arrived(uint8_t *section)
{
  printf("%s\n", __func__);
}

/*****************************************************/
struct xmpp_resource {
  char                 *res;
  struct xmpp_resource *next;
};
struct xmpp_roster {
  char                 *account;
  struct xmpp_resource *resource;
  struct xmpp_roster   *next;
};

static void xmpp_register_listeners(void);
static void service_stat_listener(srv_stat_t s);
static void roster_status_listener(const roster_status_t *s);
static int  xmpp_service_check_timeout(eloop_t *eloop, void *param);
static void print_rosters(roster_t *rs);
static void setup_online_rosters(roster_t *rs);
static void clean_online_rosters(void);
static void update_online_rosters(const roster_status_t *s);
static void initiative_p2p_event(p2p_session_t *p2p, int events, int detail_error);
static void setup_ecm_cw_tunnel(const char *jid);

static struct xmpp_roster  *online_roster = NULL;
static p2p_session_t *initiative_p2p = NULL;

static uint8_t ecm_info[1024];
static int     ecm_info_len = 0;

static int
thread_create(pthread_t *handle, void *(*start)(void *), void *param, int deatch)
{
  pthread_attr_t *a = NULL;
  pthread_attr_t  attr;
  if (deatch)
  {
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    a = &attr;
  }
  return pthread_create(handle, a, start, param);
}

static void*
xmpp_service_loop(void *param)
{
  eloop_t *xloop = (eloop_t *)param;
  xmpp_service_try_start();
  eloop_add_timeout(xloop, 4000, xmpp_service_check_timeout, NULL);

  eloop_run(xloop);

  return NULL;
}

static int
xmpp_service_check_timeout(eloop_t *eloop, void *param)
{
  srv_stat_t  state;
  static int  count = 0;

  state = xmpp_get_service_state();
    
  if (state == XMPP_STATE_NONE) 
  {
    xmpp_service_try_start();
    count = 0;
  }
  else if (state == XMPP_STATE_READY) 
  {
    if (count == 0) 
    {
      const char *jid = xmpp_get_service_jid();
      printf("xmpp service login as: %s\n", jid);
      free((void*)jid);
      xmpp_register_listeners();
      setup_ecm_cw_tunnel("mdtest001@gmail.com");
    }
    count += 1;
  }
  return 1;
}

static void
xmpp_register_listeners(void)
{
  roster_t   *rs;

  xmpp_set_service_listener(service_stat_listener);

  rs = xmpp_get_rosters();
  print_rosters(rs);
  setup_online_rosters(rs);
  xmpp_free_rosters(rs);

  xmpp_set_roster_listener(roster_status_listener);
}


static void
service_stat_listener(srv_stat_t s) 
{
  if (s == XMPP_STATE_CLOSED) 
  {
    int err = xmpp_lasterror();
    printf("service exit: %d(%s)\n", err, xmpp_strerror(err));
    clean_online_rosters();
    if(initiative_p2p!=NULL)
    {
      xmpp_p2p_close(initiative_p2p);
	  initiative_p2p = NULL;
    }
  }
}

static void
roster_status_listener(const roster_status_t *s) 
{
  printf("%s become %s\n", s->jid, s->show == XMPP_SHOW_OFFLINE ? "offline" : "online");
  update_online_rosters(s);

  if (s->show == XMPP_SHOW_OFFLINE) 
  {
    if (initiative_p2p)
    {
      const char *jid = xmpp_p2p_get_peer(initiative_p2p);
      if (jid && (strcmp(jid, s->jid) == 0)) 
      {
        printf("del p2p to %s\n", jid);
        //xmpp_p2p_post_event(initiative_p2p, P2P_EVENT_CLOSE);
		xmpp_p2p_close(initiative_p2p);
		initiative_p2p = NULL;
      }
    }
  }
}

static void
print_rosters(roster_t *rs)
{
  while(rs != NULL) 
  {
	jid_res_t *res;
	printf("roster: %s(name:%s, subcriptions:%s, %s)\n",
	        rs->account, rs->name ? rs->name : "",
			rs->sub==XMPP_ROSTER_BOTH ? "both" : "none",
			rs->respart ? "online" : "offline");

	res = rs->respart;
	if (res) 
	{
	  printf("online resource:\n");
	  while(res) 
	  {
		printf("%s/%s priority=%d\n", rs->account, res->resource, res->prio);
		res = res->next;
	  }
	}
	rs = rs->next;
  }
}

static void
setup_online_rosters(roster_t *rs) 
{
  while(rs != NULL) 
  {
    struct xmpp_roster *roster;
    jid_res_t *jidres;
    jidres = rs->respart;
    roster = NULL;
    while (jidres) 
	{
      struct xmpp_resource *resource;
      if (roster == NULL) 
	  {
        roster = (struct xmpp_roster *)malloc(sizeof(*roster));
        roster->account = strdup(rs->account);
        roster->resource = NULL;
      }
      resource = (struct xmpp_resource *)malloc(sizeof(*resource));
      resource->res = strdup(jidres->resource);
      resource->next = roster->resource;
      roster->resource = resource;

      jidres = jidres->next;
    }
    if (roster) 
	{
      roster->next = online_roster;
      online_roster = roster;
    }

    rs = rs->next;
  }
}

static void 
update_online_rosters(const roster_status_t *s)
{
  char *account, *res;

  account = strdup(s->jid);
  res     = strdup(s->jid);

  char *c = strchr(account, '/');
  if (c) {
    *c = '\0';
    c += 1;
    char *tmp = res;
    while(*c) {
      *tmp++ = *c++;
    }
    *tmp = '\0';
  }

  struct xmpp_roster *walk, *prev_walk = NULL;
  for (walk = online_roster; walk; walk = walk->next) 
  {
    if (strcmp(walk->account, account) == 0) 
	{
      struct xmpp_resource *resource = walk->resource, *prev_resource = NULL;
      while(resource) 
	  {
        if (strcmp(resource->res, res) == 0)
		{
          if (s->show == XMPP_SHOW_OFFLINE) 
		  {
            if (prev_resource == NULL) walk->resource = resource->next;
            else prev_resource->next = resource->next;
            free(resource->res);
            free(resource);

            if (walk->resource == NULL) 
			{
              if (prev_walk == NULL) online_roster = walk->next;
              else prev_walk->next = walk->next;
              free(walk->account);
              free(walk);
            }
          }
          goto __done;
        }
        prev_resource = resource;
        resource = resource->next;
      }
      if (resource == NULL) 
	  {
        if (s->show != XMPP_SHOW_OFFLINE) 
		{
          struct xmpp_resource *resource = (struct xmpp_resource*)malloc(sizeof(*resource));
          resource->res = strdup(res);
          resource->next = walk->resource;
          walk->resource = resource;
        }
      }
      goto __done;
    }
    prev_walk = walk;
  }
  if (walk == NULL) {
    if (s->show != XMPP_SHOW_OFFLINE) {
      walk = (struct xmpp_roster*)malloc(sizeof(*walk));
      walk->account = strdup(account);
      walk->resource = NULL;

      struct xmpp_resource *resource = (struct xmpp_resource*)malloc(sizeof(*resource));
      resource->res = strdup(res);
      resource->next = walk->resource;
      walk->resource = resource;

      walk->next = online_roster;
      online_roster = walk;
    }
  }

__done:
  free(account);
  free(res);
}

static void clean_online_rosters(void)
{
  struct xmpp_roster   *walk, *tw;
  struct xmpp_resource *resource, *tr;
  walk = online_roster;
  online_roster = NULL;
  for (;walk;) {
    for (resource = walk->resource; resource;) {
      tr = resource->next;
      free(resource->res);
      free(resource);
      resource = tr;
    }
    tw = walk->next;
    free(walk->account);
    free(walk);
    walk = tw;
  }
}

static p2p_session_t* 
create_p2ptunnel_for_account(struct xmpp_roster *rosters,
              const char* account, void (*event_cb)(p2p_session_t *, int, int)) 
{
  while(rosters) 
  {
    if (rosters->resource) 
    {
      if (strcmp(rosters->account, account) == 0) 
      {
        struct xmpp_resource *resource = rosters->resource;
        while(resource) 
        {
          printf("account: %s, resource: %s\n", rosters->account, resource->res);
          if (strncmp(resource->res, "jingle", 6) == 0)
            break;
          resource = resource->next;
        }
        if (resource) 
        {
          char *jid = (char *)malloc(strlen(account) + strlen(resource->res) + 2);
          sprintf(jid, "%s/%s", account, resource->res);
          printf("make a p2ptunnel request to %s ...\n", jid);
          p2p_session_t *p2p = xmpp_p2p_create_with_callback(jid, "just_a_test", event_cb);
          free(jid);
          return p2p;
        }
      }
    }
    rosters = rosters->next;
  }
  return NULL;
}

static void
parse_p2p_buf(uint8_t *buf)
{
  uint8_t *pbuf;
  uint8_t cmd;

  if(strncmp((char*)buf, "XMPPSHAREV1.1", 13) != 0)
    return;
  pbuf = &buf[13];
  cmd = pbuf[0];
  pbuf += 3;
  if (cmd == 0x04)
  {
    printf("set cw!\n");
    descrambler_set_cw(&pbuf[14], ECM_VIDEO|ECM_AUDIO);
  }
}

static void
initiative_p2p_event(p2p_session_t *p2p, int events, int detail_error) 
{
  size_t  written, read;
  int    error = 0;
  char   buf[1024];
printf("%s events=%x\n", __func__, events);
  if (events & P2P_EVENT_READ)
  {
    int ret = xmpp_p2p_read(p2p, buf, sizeof(buf), &read, &error);
	if (ret == P2P_SR_SUCCESS)
	{
	  parse_p2p_buf((uint8_t*)buf);
	}
  }
  else if(events & P2P_EVENT_WRITE)
  {
    strcpy(buf, "XMPPSHAREV1.1");
	memcpy(&buf[13], ecm_info, ecm_info_len);
	xmpp_p2p_write(p2p, buf, ecm_info_len + 13, &written, &error);
  }
  else if(events & P2P_EVENT_CLOSE)
  {
    printf("%s close event, p2p peer: %s\n", __func__, xmpp_p2p_get_peer(p2p));
    initiative_p2p = NULL;
    xmpp_p2p_close(p2p);
  }
}

static void
setup_ecm_cw_tunnel(const char *jid)
{
  initiative_p2p = create_p2ptunnel_for_account(online_roster, jid, initiative_p2p_event);
}

static int
xmpp_send_ecm(uint8_t *section, int types, short pid)
{
  int ecmlen;
  int len;
printf("initiative_p2p=%p\n", initiative_p2p);
  if (initiative_p2p)
  {
    desctx_t *c = get_descrambler_context();

    ecm_info[0] = 0x03;
	ecm_info[1] = 0;
	ecm_info[2] = 0;
	ecm_info[3] = 2;

	ecm_info[4] = (c->freq >> 8) & 0xff;
	ecm_info[5] = c->freq & 0xff;

	ecm_info[6] = c->qamsize;

	ecm_info[7] = (c->pmtpid >> 8) & 0xff;
	ecm_info[8] = c->pmtpid & 0xff;

	ecm_info[9] = 0xff;
	ecm_info[10] = 0xff;

	ecm_info[11] = 0x30;
	ecm_info[12] = 0x00;

	ecm_info[13] = (uint8_t)(types & 0x07);

	ecm_info[14] = (pid >> 8) & 0xff;
	ecm_info[15] = pid & 0xff;

	ecmlen = ((section[1] & 0x0f) << 8) + section[2] + 3;
	ecm_info[16] = (ecmlen >> 8) & 0xff;
	ecm_info[17] = ecmlen & 0xff;
	memcpy(&ecm_info[18], section, ecmlen);

	len = 15 + ecmlen;
	ecm_info[1] = (len >> 8) & 0xff;
	ecm_info[2] = len & 0xff;

	ecm_info_len = len + 3;

    xmpp_p2p_post_event(initiative_p2p, P2P_EVENT_WRITE);
	return 0;
  }
  return -1;
}

